<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="FeN7y" id="FeN7y"><span data-lake-id="uf60d51cc" id="uf60d51cc">典型回答</span></h1>
  <p data-lake-id="ub00b293b" id="ub00b293b"><br></p>
  <p data-lake-id="ua15e287d" id="ua15e287d"><span data-lake-id="u1bccfac9" id="u1bccfac9">TCC是一种分布式的事务的方案，将一个事务分成了TRY-CANCEL-CONFIRM三个阶段：</span></p>
  <p data-lake-id="u1a670698" id="u1a670698"><span data-lake-id="ua8c20824" id="ua8c20824">​</span><br></p>
  <p data-lake-id="u322aa155" id="u322aa155"><br></p>
  <p data-lake-id="u02a960ae" id="u02a960ae"><span data-lake-id="uec5f9d3f" id="uec5f9d3f">在TCC中，存在着两个比较关键的问题，那就是空回滚和悬挂的问题。</span></p>
  <p data-lake-id="u89fc9a91" id="u89fc9a91"><br></p>
  <ol list="uebbb0cd2">
   <li fid="ufa61e40c" data-lake-id="ue6f32762" id="ue6f32762"><strong><span data-lake-id="u6501f0ca" id="u6501f0ca">空回滚问题</span></strong><span data-lake-id="ubd80e7e9" id="ubd80e7e9">：TCC中的Try过程中，有的参与者成功了，有的参与者失败了，这时候就需要所有参与者都执行Cancel，这时候，对于那些没有Try成功的参与者来说，本次回滚就是一次空回滚。需要在业务中做好对空回滚的识别和处理，否则就会出现异常报错的情况，甚至可能导致Cancel一直失败，最终导致整个分布式事务失败。</span></li>
   <li fid="ufa61e40c" data-lake-id="u6b589960" id="u6b589960"><strong><span data-lake-id="uce922357" id="uce922357">悬挂事务问题</span></strong><span data-lake-id="u1a82f41d" id="u1a82f41d">：TCC的实现方式存在悬挂事务的问题，</span><span data-lake-id="ua05c01e1" id="ua05c01e1" style="color: rgb(36, 41, 46)">在调用TCC服务的一阶段Try操作时，可能会出现因网络拥堵而导致的超时，此时事务协调器会触发二阶段回滚，调用TCC服务的Cancel操作；在此之后，拥堵在网络上的一阶段Try数据包被TCC服务收到，出现了二阶段Cancel请求比一阶段Try请求先执行的情况</span><span data-lake-id="u770e714d" id="u770e714d">。</span><u><span data-lake-id="uab9157d6" id="uab9157d6">举一个比较常见的具体场景</span></u><span data-lake-id="u98d661f8" id="u98d661f8">：一次分布式事务，先发生了Try，但是因为有的节点失败，又发生了Cancel，而下游的某个节点因为网络延迟导致先接到了Cancel，在空回滚完成后，又接到了Try的请求，然后执行了，这就会导致这个节点的Try占用的资源无法释放，也没人会再来处理了，就会导致了事务悬挂。</span></li>
  </ol>
  <p data-lake-id="u999679ee" id="u999679ee"><span data-lake-id="ud042e4de" id="ud042e4de">​</span><br></p>
  <p data-lake-id="ub800a66b" id="ub800a66b"><span data-lake-id="u0b51b0e5" id="u0b51b0e5">这两个问题处理不好，都可能会导致一个分布式事务没办法保证最终一致性。有一个办法，可以一次性的解决以上两个问题，那就是——引入分布式事务记录表。</span></p>
  <p data-lake-id="ua480a828" id="ua480a828"><span data-lake-id="u86df6579" id="u86df6579">​</span><br></p>
  <p data-lake-id="u048a77b0" id="u048a77b0"><span data-lake-id="u2c839ea9" id="u2c839ea9">有了这张表，每一个参与者，都可以在本地事务执行的过程中，同时记录一次分布式事务的操作记录。</span></p>
  <p data-lake-id="u5d946706" id="u5d946706"><span data-lake-id="ufa98cc8d" id="ufa98cc8d">​</span><br></p>
  <p data-lake-id="ucc47ef3f" id="ucc47ef3f"><span data-lake-id="u7e4152f6" id="u7e4152f6">这张表中有两个关键的字段，一个是tx_id用于保存本次处理的事务ID，还有一个就是state，用于记录本次事务的执行状态。至于其他的字段，比如一些业务数据，执行时间、业务场景啥的，就自己想记录上就记录啥。</span></p>
  <p data-lake-id="u392345d8" id="u392345d8"><span data-lake-id="u31d75127" id="u31d75127">​</span><br></p>
  <pre lang="java"><code>
CREATE TABLE `distribute_transaction` (
  `tx_id` varchar(128) NOT NULL COMMENT '事务id',
  `state` int(1) DEFAULT NULL COMMENT '事务状态，0:try，1:confirm，2:cancel',
  PRIMARY KEY (`tx_id`) U
) 
</code></pre>
  <p data-lake-id="ue46943e0" id="ue46943e0"><br></p>
  <p data-lake-id="u1d52b233" id="u1d52b233"><span data-lake-id="u605745d8" id="u605745d8">有了这张表以后，我们在做try、cancel和confirm操作之后，都需要在本地事务中创建或者修改这条记录。一条记录的状态机如下：</span></p>
  <p data-lake-id="u7675eb0b" id="u7675eb0b"><span data-lake-id="ua927e567" id="ua927e567">​</span><br></p>
  <p data-lake-id="u9ad648d3" id="u9ad648d3"><span data-lake-id="uacd78b57" id="uacd78b57">​</span><br></p>
  <p data-lake-id="ua6e27fc3" id="ua6e27fc3"><strong><span data-lake-id="u09fc0e06" id="u09fc0e06">空回滚解决</span></strong><span data-lake-id="u6bc04b33" id="u6bc04b33">：当一个参与者接到一次Cancel请求的时候，先去distribute_transaction表中根据tx_id查询是否有try的记录，如果没有，则进行一次空回滚即可。并在distribute_transaction中创建一条记录，状态标记为cancel。</span></p>
  <p data-lake-id="u5a259ca1" id="u5a259ca1"><span data-lake-id="u99246ca2" id="u99246ca2">​</span><br></p>
  <p data-lake-id="u43d8d87b" id="u43d8d87b"><strong><span data-lake-id="u5f94d309" id="u5f94d309">事务悬挂解决</span></strong><span data-lake-id="u5301cc0b" id="u5301cc0b">：当一个参与者接到一次Try请求的时候，先去distribute_transaction表中根据tx_id查询是否有记录，如果当前存在，并且记录的状态是cancel，则拒绝本次try请求。</span></p>
  <p data-lake-id="ue924a79f" id="ue924a79f"><span data-lake-id="ua34acdb2" id="ua34acdb2">​</span><br></p>
  <p data-lake-id="u200afeb3" id="u200afeb3"><span data-lake-id="u2fb2b0eb" id="u2fb2b0eb">但是需要注意的是，上面的请求过程，需要做好并发控制。</span></p>
  <p data-lake-id="u1bcbc5bf" id="u1bcbc5bf"><span data-lake-id="u8b9ad5ec" id="u8b9ad5ec">​</span><br></p>
  <p data-lake-id="ua7a07b8c" id="ua7a07b8c"><span data-lake-id="ud37c1c88" id="ud37c1c88">有了这张表，我们还可以基于他做幂等控制，每次try-cancel-confirm请求来的时候，都可以到这张表中查一下，然后做幂等控制。</span></p>
 </body>
</html>